#include "wl_audio.h"
#include "ql_audio.h"
#include "ql_rtos.h"
#include "wl_malloc.h"
#include "wl_trace.h"

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define QL_PCM_BLOCK_FLAG (0x01)
#define QL_PCM_NONBLOCK_FLAG (0x02)
#define QL_PCM_READ_FLAG (0x04)
#define QL_PCM_WRITE_FLAG (0x08)

typedef enum {
    WL_AUDIO_START_PLAY,
    WL_AUDIO_STOP_PLAY,
    WL_AUDIO_PLAY_DATA,
    WL_AUDIO_START_RECORD,
    WL_AUDIO_STOP_RECORD,
} wl_audio_event_type;

typedef struct {
    wl_audio_player_callback player_cb;
    wl_audio_end_callback    player_end_cb;
    PCM_HANDLE_T             player_handle;
    ql_task_t                player_thread;
    ql_queue_t               player_buffer_queue;
    ql_mutex_t               lock;
    wl_bool                  is_playing;
    wl_bool                  is_exiting;
    wl_bool                  has_exception;
} wl_audio_player;

typedef struct {
    wl_audio_recorder_callback recorder_cb;
    wl_audio_end_callback      recorder_end_cb;
    PCM_HANDLE_T               recorder_handle;
    ql_task_t                  recorder_thread;
    ql_queue_t                 recorder_buffer_queue;
    wl_bool                    is_recording;
    wl_bool                    is_exiting;
    wl_bool                    has_exception;
} wl_audio_recorder;

typedef struct {
    wl_audio_event_type dataType;
    wl_uint8*           p_data;
    wl_int32            data_size;
} wl_audio_event_data;

wl_int32 wl_audio_init(void) {
    return 0;
}

wl_int32 wl_audio_end(void) {
    return 0;
}

void wl_audio_recorder_task_handle(void* p_param) {
    wl_audio_recorder*  p_recorder = (wl_audio_recorder*)p_param;
    int                 count;
    wl_uint8            buffer[RECORD_PCM_FRAME_BYTES];
    wl_audio_event_data data;

    if (p_recorder) {
        p_recorder->is_exiting = wl_false;
        while (!p_recorder->is_exiting) {
            if (0
                == ql_rtos_queue_wait(
                    p_recorder->recorder_buffer_queue, (wl_uint8*)&data, sizeof(wl_audio_event_data), 0)) {
                if (data.dataType == WL_AUDIO_START_RECORD) {
                    WLLOGD("start record!!!!!");
                    p_recorder->is_recording = wl_true;
                } else if (data.dataType == WL_AUDIO_STOP_RECORD) {
                    WLLOGD("stop record!!!!");
                    p_recorder->has_exception = wl_false;
                    p_recorder->is_exiting    = wl_true;
                    p_recorder->is_recording  = wl_false;
                    break;
                }
            }

            if (p_recorder->is_recording) {
                count = ql_pcm_read(p_recorder->recorder_handle, buffer, RECORD_PCM_FRAME_BYTES);
                if (count > 0) {
                    if (p_recorder->recorder_cb) {
                        p_recorder->recorder_cb(p_recorder, buffer, count);
                    }
                } else if (count < 0) {
                    if (p_recorder->recorder_handle) {
                        ql_pcm_close(p_recorder->recorder_handle);
                        p_recorder->recorder_handle = wl_null;
                    }

                    p_recorder->has_exception = wl_true;
                    p_recorder->is_exiting    = wl_true;
                    p_recorder->is_recording  = wl_false;
                    WLLOGW("recording error!!!!!!!!!!!!!!!!!");
                    break;
                } else {
                    ql_rtos_task_sleep_ms(50);
                }
            } else {
                ql_rtos_task_sleep_ms(10);
            }
        }

        if (p_recorder->recorder_end_cb) {
            if (!p_recorder->has_exception) {
                p_recorder->recorder_end_cb(p_recorder, 0);
            } else {
                p_recorder->recorder_end_cb(p_recorder, -1);
            }
        }

        ql_rtos_queue_delete(p_recorder->recorder_buffer_queue);
        ql_rtos_task_delete(p_recorder->recorder_thread);
        wl_free(p_recorder);
    }
}

WL_AUDIO_HANDLE wl_audio_start_record(wl_audio_recorder_callback callback, wl_audio_end_callback end_callback) {
    wl_audio_recorder* p_recorder = (wl_audio_recorder*)wl_malloc(sizeof(wl_audio_recorder));
    WLLOGD("wl_audio_start_record allocate recorder:%x", p_recorder);
    if (p_recorder) {
        memset(p_recorder, 0, sizeof(wl_audio_recorder));
        if (0 != ql_rtos_queue_create(&p_recorder->recorder_buffer_queue, sizeof(wl_audio_recorder), 100)) {
            wl_free(p_recorder);
            return wl_null;
        }

        QL_PCM_CONFIG_T config;
        config.channels             = 1;
        config.samplerate           = RECORD_PCM_RATE;
        config.periodcnt            = 0;
        p_recorder->recorder_handle = ql_pcm_open(&config, QL_PCM_BLOCK_FLAG | QL_PCM_READ_FLAG);
        if (p_recorder->recorder_handle == wl_null) {
            ql_rtos_queue_delete(p_recorder->recorder_buffer_queue);
            wl_free(p_recorder);
            return wl_null;
        }

        ql_set_audio_path_earphone();

        p_recorder->recorder_cb     = callback;
        p_recorder->recorder_end_cb = end_callback;
        p_recorder->has_exception   = wl_false;
        p_recorder->is_exiting      = wl_true;

        if (0
            != ql_rtos_task_create(&p_recorder->recorder_thread,
                                   4 * 1024,
                                   100,
                                   "recorder task",
                                   wl_audio_recorder_task_handle,
                                   p_recorder)) {
            ql_pcm_close(p_recorder->recorder_handle);
            wl_free(p_recorder);
            p_recorder = wl_null;
        } else {
            wl_audio_event_data q_data;
            while (p_recorder->is_exiting) {
                ql_rtos_task_sleep_ms(10);
            }
            q_data.dataType  = WL_AUDIO_START_RECORD;
            q_data.p_data    = wl_null;
            q_data.data_size = 0;
            ql_rtos_queue_release(
                p_recorder->recorder_buffer_queue, sizeof(wl_audio_event_data), (wl_uint8*)&q_data, QL_WAIT_FOREVER);
            WLLOGD("after send start record");
        }
    }

    return (WL_AUDIO_HANDLE)p_recorder;
}

wl_int32 wl_audio_stop_record(WL_AUDIO_HANDLE handle) {
    wl_audio_recorder* p_recorder = (wl_audio_recorder*)handle;
    WLLOGD("stop record................%d", p_recorder->is_recording);
    if (p_recorder && p_recorder->is_recording) {
        wl_audio_event_data q_data;

        if (p_recorder->recorder_handle) {
            ql_pcm_close(p_recorder->recorder_handle);
            p_recorder->recorder_handle = wl_null;
        }

        q_data.dataType          = WL_AUDIO_STOP_RECORD;
        q_data.p_data            = NULL;
        q_data.data_size         = 0;
        p_recorder->is_recording = wl_false;
        ql_rtos_queue_release(
            p_recorder->recorder_buffer_queue, sizeof(wl_audio_event_data), (wl_uint8*)&q_data, QL_WAIT_FOREVER);
        WLLOGD("after send stop record");
    }

    return 0;
}

void wl_audio_player_task_handle(void* p_param) {
    wl_audio_player*    p_player = (wl_audio_player*)p_param;
    wl_audio_event_data data;

    if (p_player) {
        p_player->is_exiting = wl_false;

        while (!p_player->is_exiting) {
            int r = 0;
            ql_rtos_mutex_lock(p_player->lock, QL_WAIT_FOREVER);
            r = ql_rtos_queue_wait(p_player->player_buffer_queue, (wl_uint8*)&data, sizeof(wl_audio_event_data), 0);
            ql_rtos_mutex_unlock(p_player->lock);
            if (0 == r) {
                if (p_player->is_playing && data.dataType == WL_AUDIO_PLAY_DATA) {
                    int pcm_write = ql_pcm_write(p_player->player_handle, data.p_data, data.data_size);
                    wl_free(data.p_data);
                    if (pcm_write < 0) {
                        WLLOGW("write data error!!!!!!!!!!!!!!!!!!!!!!!!!!!");
                        if (p_player->player_handle) {
                            ql_pcm_close(p_player->player_handle);
                            p_player->player_handle = wl_null;
                        }
                        p_player->has_exception = wl_true;
                        p_player->is_playing    = wl_false;
                        p_player->is_exiting    = wl_true;
                        break;
                    }
                } else if (data.dataType == WL_AUDIO_START_PLAY) {
                    p_player->is_playing = wl_true;
                    WLLOGD("start play");
                } else if (data.dataType == WL_AUDIO_STOP_PLAY) {
                    WLLOGD("stop play");
                    p_player->has_exception = wl_false;
                    p_player->is_exiting    = wl_true;
                    break;
                }
            }

            if (p_player->player_cb && p_player->is_playing) {
                p_player->player_cb(p_player);
            }

            ql_rtos_task_sleep_ms(20);
        }

        if (p_player->player_end_cb) {
            if (!p_player->has_exception) {
                p_player->player_end_cb(p_player, 0);
            } else {
                p_player->player_end_cb(p_player, -1);
            }
        }

        ql_rtos_queue_delete(p_player->player_buffer_queue);
        ql_rtos_mutex_delete(p_player->lock);
        ql_rtos_task_delete(p_player->player_thread);
        wl_free(p_player);

        WLLOGD("play task is finish.............");
    }
}

WL_AUDIO_HANDLE wl_audio_start_play(wl_audio_player_callback callback, wl_audio_end_callback end_callback) {
    QL_PCM_CONFIG_T  config;
    wl_audio_player* p_player = (wl_audio_player*)wl_malloc(sizeof(wl_audio_player));
    WLLOGD("wl_audio_start_play: p_player:%x", p_player);
    if (p_player) {
        memset(p_player, 0, sizeof(wl_audio_player));
        if (0 != ql_rtos_queue_create(&p_player->player_buffer_queue, sizeof(wl_audio_event_data), 100)) {
            wl_free(p_player);
            return wl_null;
        }

        if (0 != ql_rtos_mutex_create(&p_player->lock)) {
            ql_rtos_queue_delete(p_player->player_buffer_queue);
            wl_free(p_player);
            p_player = wl_null;
            return wl_null;
        }

        config.channels         = 1;
        config.samplerate       = PLAY_PCM_RATE;
        config.periodcnt        = 0;
        p_player->player_handle = ql_pcm_open(&config, QL_PCM_WRITE_FLAG | QL_PCM_NONBLOCK_FLAG);

        if (p_player->player_handle == NULL) {
            WLLOGW("start player fail!!!!!!!!!!!!!!!!");
            ql_rtos_mutex_delete(p_player->lock);
            ql_rtos_queue_delete(p_player->player_buffer_queue);
            wl_free(p_player);
            return wl_null;
        }

        ql_set_audio_path_earphone();
        ql_set_volume(AUDIOHAL_SPK_VOL_7);

        p_player->player_cb     = callback;
        p_player->player_end_cb = end_callback;
        p_player->has_exception = wl_false;
        p_player->is_exiting    = wl_true;

        if (0
            != ql_rtos_task_create(
                &p_player->player_thread, 4 * 1024, 100, "player task", wl_audio_player_task_handle, p_player)) {
            ql_rtos_queue_delete(p_player->player_buffer_queue);
            ql_rtos_mutex_delete(p_player->lock);
            wl_free(p_player);
            p_player = wl_null;
        } else {
            wl_audio_event_data q_data;
            WLLOGD("after create player task................");
            while (p_player->is_exiting) {
                ql_rtos_task_sleep_ms(10);
            }

            WLLOGD("sending play start player event to task");

            q_data.dataType  = WL_AUDIO_START_PLAY;
            q_data.p_data    = NULL;
            q_data.data_size = 0;
            ql_rtos_queue_release(
                p_player->player_buffer_queue, sizeof(wl_audio_event_data), (wl_uint8*)&q_data, QL_WAIT_FOREVER);

            WLLOGD("after sending event to play task");
        }
    }

    return (WL_AUDIO_HANDLE)p_player;
}

wl_int32 wl_audio_stop_play(WL_AUDIO_HANDLE handle) {
    wl_audio_player* p_player = (wl_audio_player*)handle;
    WLLOGD("wl_audio_stop_play");
    if (p_player && p_player->is_playing) {
        wl_audio_event_data q_data;

        if (p_player->player_handle) {
            ql_pcm_close(p_player->player_handle);
            p_player->player_handle = wl_null;
        }

        p_player->is_playing = wl_false;
        q_data.dataType      = WL_AUDIO_STOP_PLAY;
        q_data.p_data        = NULL;
        q_data.data_size     = 0;
        ql_rtos_mutex_lock(p_player->lock, QL_WAIT_FOREVER);
        ql_rtos_queue_release(
            p_player->player_buffer_queue, sizeof(wl_audio_event_data), (wl_uint8*)&q_data, QL_WAIT_FOREVER);
        ql_rtos_mutex_unlock(p_player->lock);
    }

    return 0;
}

wl_int32 wl_audio_put_play_data(WL_AUDIO_HANDLE handle, wl_uint8* data, wl_uint32 data_size) {
    wl_audio_player* p_player = (wl_audio_player*)handle;
    if (p_player) {
        if (p_player->is_playing) {
            wl_audio_event_data q_data;
            q_data.dataType = WL_AUDIO_PLAY_DATA;
            q_data.p_data   = (wl_uint8*)wl_malloc(data_size);
            if (q_data.p_data) {
                memcpy(q_data.p_data, data, data_size);
                q_data.data_size = data_size;
                ql_rtos_mutex_lock(p_player->lock, QL_WAIT_FOREVER);
                ql_rtos_queue_release(
                    p_player->player_buffer_queue, sizeof(wl_audio_event_data), (wl_uint8*)&q_data, QL_WAIT_FOREVER);
                ql_rtos_mutex_unlock(p_player->lock);
            }
        }

        return 0;
    }

    return -1;
}

#ifdef __cplusplus
}
#endif /* __cplusplus */
